// eee.Sheffield.PZ.Imaging
//
// Copyright ?Ping Zou, 2007
// sg71.cherub@gmail.com

namespace eee.Sheffield.PZ.Imaging
{
    using System;
    using System.IO;
    using System.Drawing;
    using System.Drawing.Imaging;
    using eee.Sheffield.PZ.Math;
    using System.Collections.Generic;
    using eee.Sheffield.PZ.Imaging.ImageFilter;

    /// <summary>
    /// Look Ahead Detection, in a 4r windows, detect centre lines to guide tracking.
    /// </summary>
    public class LookAheadDetection
    {
        #region Fields
        private PZPoint _initcp = null;		// initial center point;
        private PZDirection _initvd = null;	// initial vessel direction;
        private double _meanr = 0.0;		// mean r estimate
        private int _background;
        private List<TrackResult> _trackHistoryList;
        private int _sisThreshold;

        public List<LineSegment> _candidateCentreLineSegmentList = new List<LineSegment>(); // candidate centre line segment list
        //public List<PZDirection> _candidateDirectionList = new List<PZDirection>();
        //public List<PZPoint> _lastCPRecordList = new List<PZPoint>();

        public List<PZPoint> _cpCandidateList;
        public List<PZDirection> _vdCandidateList;
        public List<double> _rCandidateList;
        #endregion

        #region Constructors
        public LookAheadDetection(
            PZPoint inputcp, PZDirection inputvd, double inputr, int background, List<TrackResult> trackHistoryList)
        {
            _initcp = inputcp;
            _initvd = inputvd;
            _meanr = inputr;
            _background = background;
            _trackHistoryList = trackHistoryList;
        } // LookAheadDetection(PZPoint, PZDirection, double) 
        #endregion

        /// <summary>
        /// look ahead detection, search centre line segments in a 4r windows
        /// </summary>
        /// <param name="srcImg"></param>
        /// <returns></returns>
        public void Apply(Bitmap srcImg)
        {
            // -- prepare varibales;
            int width = srcImg.Width;
            int height = srcImg.Height;

            // 1. pick a rK-by-rK windows, k = 8
            int currentX = (int)System.Math.Round(_initcp.x);
            int currentY = (int)System.Math.Round(_initcp.y);
            int k = 8;  //  @@@@
            int rKMin = 31; // @@@@
            int rKMax = 51; // @@@@
            int rK = k * _meanr < rKMin ? rKMin : (int)(k * _meanr);
            rK = rK > rKMax ? rKMax : rK;
            int rKHalf = rK / 2;
            int upper = currentY - rKHalf > 0 ? currentY - rKHalf : 0;
            int left = currentX - rKHalf > 0 ? currentX - rKHalf : 0;
            int yOffset = upper;
            int xOffset = left;
            int localWidth = left + rK < width ? rK : width - 1 - left;
            int localHeight = upper + rK < height ? rK : height - 1 - upper;
            if (localWidth < 4 || localHeight < 4)
            {
                _cpCandidateList = new List<PZPoint>();
                _vdCandidateList = new List<PZDirection>();
                _rCandidateList = new List<double>();
                return;
            }
            ConvertBitmapMatrix converter = new ConvertBitmapMatrix();
            PZMath_matrix srcMatrix = converter.ConvertGraylevelBitmapToMatrix(srcImg);
            PZMath_matrix localWindowMatrix = srcMatrix.Submatrix(upper, left, localHeight, localWidth);
            Bitmap localWindow = converter.ConvertMatrixToGraylevelBitmap(localWindowMatrix);            
            localWindow.Save("[temp] 1 local window.bmp", ImageFormat.Bmp);

            // 2. adaptive smooth
            AdaptiveSmooth adaptiveSmooth = new AdaptiveSmooth();
            Bitmap smoothed = adaptiveSmooth.ApplyGraylevelImageFilter(localWindow);
            smoothed.Save("[temp] 2 smoothed local window.bmp", ImageFormat.Bmp);
            

            // 3. SIS threshold
            SISThreshold sisThreshold = new SISThreshold();
            sisThreshold.ApplyGraylevelImageFilter(smoothed);
            _sisThreshold = sisThreshold.Threshold;
            //int threshold = _sisThreshold < _background ? (_sisThreshold + _background) / 2 : _background;
            int threshold = _sisThreshold;
            Threshold thresholdFilter = new Threshold(threshold);
            Bitmap thresholded = thresholdFilter.ApplyGraylevelImageFilter(smoothed);
            //Bitmap thresholded = thresholdFilter.ApplyGraylevelImageFilter(localWindow);
            thresholded.Save("[temp] 3 thresholded.bmp", ImageFormat.Bmp);
            

            // 4. closing
            Closing closing = new Closing();
            Bitmap closed = closing.ApplyGraylevelImageFilter(thresholded);
            closed.Save("[temp] 4 closed.bmp", ImageFormat.Bmp);
            
            // 5. Rockett thinning
            RockettThinning thinning = new RockettThinning();
            Bitmap thinned = thinning.ApplyGraylevelImageFilter(closed);
            thinned.Save("[temp] 5 thin.bmp", ImageFormat.Bmp);
            //Bitmap thinned = thinning.ApplyGraylevelImageFilter(thresholded);            

            // 6. disregard those thinning result inside centre 2r-by-2r windows
            int intr = (int)System.Math.Round(2 * _meanr);
            upper = localWidth / 2 - intr;
            left = localHeight / 2 - intr;
            upper = upper > 0 ? upper : 1;
            left = left > 0 ? left : 1;
            localWidth = intr * 2;
            localHeight = intr * 2;
            int right = left + localWidth;
            int lower = upper + localHeight;
            right = right < thinned.Width ? right : thinned.Width - 1;
            lower = lower < thinned.Height ? lower : thinned.Height - 1;


            //upper = _meanr < 1 ? 1 : (int)System.Math.Round(_meanr);
            //left = upper;
            //localWidth = left + rKHalf < thinned.Width ? rKHalf : thinned.Width - 1 - left;
            //localHeight = upper + rKHalf < thinned.Height ? rKHalf : thinned.Height - 1 - upper;
            //int right = left + localWidth;
            //int lower = upper + localHeight;

            PZMath_matrix thinnedMatrix = converter.ConvertGraylevelBitmapToMatrix(thinned);
            for (int x = left; x < right; x++)
            {
                for (int y = upper; y < lower; y++)
                {
                    thinnedMatrix[y, x] = 255.0;
                }
            }
            thinned = converter.ConvertMatrixToGraylevelBitmap(thinnedMatrix);
            thinned.Save("[temp] 6 centre removed thin.bmp", ImageFormat.Bmp);

            // 7. split line segments
            FreeEndDetector freeEnd = new FreeEndDetector();
            freeEnd.ApplyGraylevelImageFilter(thinned);
            TripleJunctionDetector junction = new TripleJunctionDetector();
            junction.ApplyGraylevelImageFilter(thinned);
            LineSegmentSplitter splitter = new LineSegmentSplitter(freeEnd.FreeEndList, junction.TripleJunctionList);
            splitter.ApplyGraylevelImageFilter(thinned);
            Bitmap lineSegmentImage = splitter.LineSegmentImage(localWindow);
            lineSegmentImage.Save("[temp] 7 line segment image.bmp", ImageFormat.Bmp);

            // release intermedia Bitmaps
            localWindow.Dispose();
            smoothed.Dispose();
            thresholded.Dispose();
            closed.Dispose();            
            thinned.Dispose();
            lineSegmentImage.Dispose();


            // 8. compare points on centre line segment candidate with the tracking history            
            bool isInTrackHistory;
            _cpCandidateList = new List<PZPoint>();
            _vdCandidateList = new List<PZDirection>();
            _rCandidateList = new List<double>();

            int minCentreLineSegmentLength = 5; // @@@@
            foreach (LineSegment l in splitter.LineSegmentList)
            {
                int lineSegmentLength = l.PointList.Count;
                int halfLineSegmentLength = lineSegmentLength / 2;

                if (lineSegmentLength >= minCentreLineSegmentLength)
                {
                    // find the mid-point of the line segment
                    PZPoint cpCandidate = new PZPoint(l.PointList[halfLineSegmentLength]);
                    // offset the coordinates
                    cpCandidate.x += xOffset;
                    cpCandidate.y += yOffset;
                    // search CP candidate in the tracking history
                    isInTrackHistory = false;
                    foreach (TrackResult tr in _trackHistoryList)
                    {                       
                        int trRecordCount = tr._cpArray.Count - 2;
                        for (int j = 0; j < trRecordCount; j++)
                        {
                            PZPoint cp = tr._cpArray[j];
                            double r = tr._rArray[j];
                            if (cpCandidate.Distance(cp) < r)
                            {
                                isInTrackHistory = true;
                                break;
                            }
                        }
                    }
                    if (!isInTrackHistory)
                    {
                        // accept cpCandidate                     
                        _candidateCentreLineSegmentList.Add(l);
                        _cpCandidateList.Add(cpCandidate);

                        // vd candidate
                        double xDiff = (l.PointList[halfLineSegmentLength + 1].x - l.PointList[halfLineSegmentLength - 1].x) / 2.0;
                        double yDiff = (l.PointList[halfLineSegmentLength + 1].y - l.PointList[halfLineSegmentLength - 1].y) / 2.0;
                        PZPoint temp = new PZPoint(cpCandidate.x + xDiff, cpCandidate.y + yDiff);
                        // new centre line should "lead away" from the last centre point
                        if (temp.Distance(_initcp) < cpCandidate.Distance(_initcp))
                        {
                            _vdCandidateList.Add(new PZDirection(-xDiff, -yDiff));
                        }
                        else
                        {
                            _vdCandidateList.Add(new PZDirection(xDiff, yDiff));
                        }
                    }                    
                } // if (lineSegmentLength >= minCentreLineSegmentLength)
            } // foreach

                    
                    //List<PZPoint> tempCPList = new List<PZPoint>(l.PointList.Count);
                    
                    //// find the mid-point of the line segment



                    //// offset the coordinates
                    //foreach (PZPoint p in l.PointList)
                    //{
                    //    p.x += xOffset;
                    //    p.y += yOffset;

                    //    // search centre line candidate with the tracking history                        
                    //    isInTrackHistory = false;
                    //    foreach (TrackResult tr in _trackHistoryList)
                    //    {
                    //        int trRecordCount = tr._cpArray.Count - 2;
                    //        for (int j = 0; j < trRecordCount; j++)
                    //        {
                    //            PZPoint cp = tr._cpArray[j];
                    //            double r = tr._rArray[j];
                    //            if (p.Distance(cp) < r)
                    //            {
                    //                isInTrackHistory = true;
                    //                break;
                    //            }
                    //        }
                    //        if (isInTrackHistory)
                    //            break;
                    //    }
                    //    if (!isInTrackHistory)
                    //        tempCPList.Add(p);
                    //}

                    //// find cp candidate, middle point of the centre line candidate
                    //// find vd candidate, the direction "leading away" current cp
                    //int tempCPListCount = tempCPList.Count;
                    //if (tempCPListCount >= minCentreLineSegmentLength)
                    //{
                    //    _candidateCentreLineSegmentList.Add(l);

                    //    int midIndex = tempCPListCount / 2;
                    //    // centre point candidate
                    //    PZPoint cpCandidate = new PZPoint(tempCPList[midIndex]);
                    //    _cpCandidateList.Add(cpCandidate);
                    //    // vd candidate
                    //    double xDiff = (tempCPList[midIndex + 1].x - tempCPList[midIndex - 1].x) / 2.0;
                    //    double yDiff = (tempCPList[midIndex + 1].y - tempCPList[midIndex - 1].y) / 2.0;
                    //    PZPoint temp = new PZPoint(cpCandidate.x + xDiff, cpCandidate.y + yDiff);
                    //    // new centre line should "lead away" from the last centre point
                    //    if (temp.Distance(_initcp) < cpCandidate.Distance(_initcp))
                    //    {
                    //        _vdCandidateList.Add(new PZDirection(-xDiff, -yDiff));
                    //    }
                    //    else
                    //    {
                    //        _vdCandidateList.Add(new PZDirection(xDiff, yDiff));
                    //    }
            //        }
            //    }
            //}

            // sort CP candidates by their distance to the last CP, as well as VD candidates
            //PZPoint tempP;
            //PZDirection tempD;
            //int cpCandidateCountM1 = _cpCandidateList.Count - 1;
            
            //for (int i = 0; i < cpCandidateCountM1; i++)
            //{
            //    for (int j = 0; j < cpCandidateCountM1 - i; j++)
            //    {
            //        if (_cpCandidateList[j].Distance(_initcp) > _cpCandidateList[j + 1].Distance(_initcp))
            //            // swap
            //        {
            //            tempP = _cpCandidateList[j];
            //            _cpCandidateList[j] = _cpCandidateList[j + 1];
            //            _cpCandidateList[j + 1] = tempP;

            //            tempD = _vdCandidateList[j];
            //            _vdCandidateList[j] = _vdCandidateList[j + 1];
            //            _vdCandidateList[j + 1] = tempD;
            //        }
            //    }
            //}


            // 9. estimate radius, search along scanline, find those pixels the intensity of which is smaller than SIS threshold
            // r <= 10
            int cpCandidateCount = _cpCandidateList.Count;
            int maxRCandidateLength = 10;   // @@@@
            for (int i = 0; i < cpCandidateCount; i++)
            {
                PZPoint cp = _cpCandidateList[i];
                PZDirection vd = _vdCandidateList[i];
                PZDirection sd = new PZDirection(vd);
                sd.Rotate(90);

                double leftR = 1.0;
                double rightR = 1.0;
                double tempX = cp.x;
                double tempY = cp.y;
                int intensity;

                // left wing search
                while (true)
                {
                    tempX += sd.x;
                    tempY += sd.y;

                    if (tempX < 0 || tempX >= width || tempY < 0 || tempY >= height || leftR > maxRCandidateLength)
                        break;

                    intensity = (int)srcMatrix[(int)tempY, (int)tempX];
                    if (intensity <= _sisThreshold)  // vessel
                        leftR++;
                    else
                        break;
                }

                // right wing search
                tempX = cp.x;
                tempY = cp.y;
                while (true)
                {
                    tempX -= sd.x;
                    tempY -= sd.y;

                    if (tempX < 0 || tempX >= width || tempY < 0 || tempY >= height || rightR > maxRCandidateLength)
                        break;

                    intensity = (int)srcMatrix[(int)tempY, (int)tempX];
                    if (intensity <= _sisThreshold)  // vessel
                        rightR++;
                    else
                        break;
                }
                double r = (leftR + rightR) / 2.0;
                _rCandidateList.Add(r);
            }
        } // Apply()     

        public void DrawCentreLineSegmentCandidate(Bitmap srcImage, string fileName)
        {
            //int xOffset = (int)(_initcp.x - _meanr * 2);
            //int yOffset = (int)(_initcp.y - _meanr * 2);

            Bitmap dstImage = new Bitmap(srcImage);
            eee.Sheffield.PZ.Imaging.Image.StandardizeImageTo24bppRGB(ref dstImage);
            foreach (LineSegment l in _candidateCentreLineSegmentList)
            {
                foreach (PZPoint p in l.PointList)
                {
                    //dstImage.SetPixel((int)p.x + xOffset, (int)p.y + yOffset, Color.Red);
                    dstImage.SetPixel((int)p.x, (int)p.y, Color.Red);
                }
            }

            dstImage.Save(fileName,ImageFormat.Bmp);
        }
    } // LookAheadDetection
}
